Swift 入门

特色

  • 苹果宣称 Swift 的特点是:快速、现代、安全、互动,而且明显优于 Objective-C 语言
  • 可以使用现有的 CocoaCocoa Touch 框架
  • Swift 取消了 Objective C 的指针及其他不安全访问的使用
  • 舍弃 Objective C 早期应用 Smalltalk 的语法,全面改为句点表示法
  • 提供了类似 Java 的名字空间(namespace)、泛型(generic)、运算对象重载(operator overloading)
  • Swift 被简单的形容为 “没有 C 的 Objective-C”(Objective-C without the C)

常量&变量

1
2
3
4
5
6
var i = 10
print(i)
i = 15
print(i)
let j = 20
  • 小结
    • var 定义变量,设置之后可以修改
    • let 定义常量,设置之后不可以修改
    • 语句末尾不用使用 ;
    • 在 Swift 中使用 print() 替代 OC 中的 NSLog
    • print 的性能更好,后面会演示

定义 OC 对象

1
2
3
4
5
// UIView *testView = [[UIView alloc] initWithFrame:];
let testView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
testView.backgroundColor = UIColor.redColor()
view.addSubview(testView)
1
2
3
4
5
6
7
8
/*
结构体:
OC: CGPointMake()
Swift: CGPoint()
*/
let btn = UIButton(type: UIButtonType.ContactAdd)
btn.center = CGPoint(x: 50, y: 50)
view.addSubview(btn)
  • 阶段性小结
    • Swift 中要实例化一个对象可以使用 类名() 的格式,与 OC 中的 alloc/init 等价
    • OC 中的 initWithXXXSwift 中通常可以使用 类名(XXX: ) 找到对应的函数
    • OC 中的 [UIColor redColor] 类方法,在 Swift 中通常可以使用 类名.XXX 找到对应的函数
    • 使用 let 修饰 v 并且赋值,表示 该常量的内存地址不允许修改,但是可以修改其内部的属性
    • 当前对象的属性,不需要使用 self.

常量&变量的使用原则:尽量先用 let,只有需要变的时候,再用 var,能够更加安全

变量类型

1
2
3
4
5
6
7
8
9
10
11
12
13
/*:
自动类型推导
* 如果是在定义的同时初始化, 那么可以不用写数据类型, 编译器会根据右边的内容自动推导出当前的数据类型
* 技巧: 在做Swift开发中会经常使用 option + click 查看文档
*/
var x = 10
var y = 10.5
var z: Double = 20
// 注意: 在Swift中Double和CGFloat也是需要转换的
print(Double(x) + y)
print(x + Int(y))
print(y + z)
  • 阶段性小结
    • Swift 是对类型要求非常严格的一门语言,一个值永远不会被自动转换成其他类型
    • 如果要转换,必须显示转换,Swift 中
      • 小数默认是 Double 类型
      • 整数默认是 Int 类型
    • 如果要显式的指定变量的类型,可以在定义是使用 var 变量名: 类型 = 值

      逻辑分支

简单体验

1
2
3
4
5
var i = 10
if i > 0 {
print("OK")
}
  • 阶段性小结
    • Swift 中没有 C 语言中的非零即真概念
    • 在逻辑判断时必须显示地指明具体的判断条件
    • if 语句条件的 () 可以省略
    • 但是 {} 不能省略
    • Swift中提供了真正的Bool类型, true/false, 条件表达式的值必须是true/false

三目

1
2
3
4
5
var a = 10
var b = 50
var result = a > b ? a : b
print(result)
  • 阶段性小结
    • Swift 中的 三目 运算保持了和 OC 一致的风格
    • 注意: 运算符之间需要有空格
    • 提醒: 在Swift开发中三目运算符用得非常多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*:
Switch
* switch后面的圆括号可以省略
* 如果是在OC中每个case后面不写break, 导致穿透. 而在Swift中不会
* 如果是在OC中想在case后面定义变量, 必须加上{}说明作用域, 而在Swift中不用说明作用域
* 如果是在OC中default的位置可以随便写, 而在Swift中不允许
* 如果是在OC中default可以不用写, 而在Swift中大部分情况下是必须写的
*/
let value = 3
switch value
{
case 0:
print("0")
var i = 10
case 1:
print("1")
default:
print("other")
}

可选项

演练 1

1
2
3
4
5
6
7
let url = NSURL(string: "http://www.baidu.com/")
if url != nil {
NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: { (data, _, _) -> Void in
print(NSString(data: data!, encoding: NSUTF8StringEncoding))
}).resume()
}
  • 阶段性小结
    • Swift 中,不是所有的对象实例化方法都会返回值,在实际开发中需要注意实例化函数的返回类型,例如:
1
convenience init?(string URLString: String)
* 如果有 `?` 表示该方法有可能无法实例化到正确的对象
* 这种函数返回的对象,被称为 `可选项`,即有可能有值,也有可能没有值
* 实际开发时,需要针对这种对象加以判断,并且在分支内部使用 `!`,指明改对象确实是存在的
* 相比在 `OC` 的开发,尤其在日常练习时,会给定一个能够运行的值,而在实际运行时,一旦条件不满足,会直接闪退,这样用户体验会非常不好

Swift 的设计者考虑到因为对类型的强制要求,会让代码很难看,因此提供了一个变通的解决方案

演练 2

1
2
3
4
5
if let url = NSURL(string: "http://www.baidu.com") {
NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, _, _) -> Void in
print(NSString(data: data!, encoding: NSUTF8StringEncoding))
}).resume()
}
  • 阶段性小结

    • 使用 if let 常量 = 可选构造函数 的方式能够确保分支内部常量一定是有值的
    • 并且在分支内部不再需要使用 !
    • 这是 Swift 代码中的一个非常重要的使用技巧
  • 提示

    • 尽管 Swift 提供了类型校验的手段,但是要写出 优雅 的 Swift 代码,还是需要多加练习的,否则一不小心就会出现分支嵌套层次很深的代码
    • 有关 ?! 的选择,可以借助 Xcode 的辅助工具,但是强烈建议每次遇到提示时,要多加思考,反复揣摩

演练3

1
2
3
4
5
6
7
8
var name: String?
print(name?.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
//name = "lnj"
print(name?.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
print((name?.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) ?? 0))
  • 阶段性小结
    • ?? 是一个非常有用的操作符,能够快速对 nil 进行判断
    • 如果对象是 nil,则使用 ?? 后面的值代替前面的 nil 值参与计算
    • 在使用 ?? 时,整个部分需要使用 () 包装
    • 这一技巧在 UITableView 的数据源方法中尤为重要

循环

OC风格的 for

1
2
3
4
// 传统写法
for var i = 0; i < 10; i++ {
print(i)
}

Swift风格的 for

1
2
3
4
5
6
7
8
9
10
11
// 遍历 0 ~ <10
for i in 0..<10 {
print(i)
}
print("---")
// 遍历 0 ~ 10
for i in 0...10 {
print(i)
}
  • 阶段性小结
    • Swift 中使用 in 关键字标示循环的范围
    • 0..<10 表示从0到9
    • 0...10 表示从0到10
    • 注意之间不能出现空格

特殊写法

1
2
3
for _ in 0...10 {
print("hello")
}
  • 阶段性小结
    • 如果不关心循环本身的索引,可以使用 _ 忽略

数组

数组中保存的对象类型

1
2
3
4
5
// 数组中保存的都是字符串
let arr = ["zhangsan", "lisi"]
// 特点: 自动将基本数据类型转换为对象类型
let arr1 = ["zhangsan", 1]
  • 阶段性小结
    • 数组使用 [] 定义,这一点与 OC 相同
    • 如果初始化时,所有内容类型一致,择数组中保存的是该类型的内容
    • 如果初始化时,所有内容类型不一致,择数组中保存的是 NSObject

常见数组操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 定义只能保存字符串类型数组
var array: [String]
// 初始化数组
array = ["zhangsan"]
# 实例化新的数组
// 实例化一个空数组
var array2 = [String]()
// var array2:[String] = []
array2.append("1")
array2.append("2")
# 添加元素
array.append("lisi")
# 删除元素
// 删除, 数组的删除方法会将被删除的元素返回给我们
array.removeAtIndex(1)
# 删除所有元素
// keepCapacity: 删除数组中的元素, 是否需要保持数组原有的容量, 如果传入false代表不保持容量
array.removeAll(keepCapacity: true)
print(array.capacity)
// 注意数组容量的变化
// 注意点: 数组添加容量永远是在原有容量的基础上*2
// 放得下继续放,放不下时,在此时的capacity * 2
for i in 0..<10 {
array.append("\(i)")
print("\(array) --- \(array.capacity)")
}
# 拼接数组 数组类型要相同
array += array2
# 遍历
for a in arr {
print(a)
// 像 OC 一样打印
// print(arr as NSArray)
}
  • 阶段性小结
    • 如果定义数组时指定了保存对象的类型,则不能向数组中添加其他类型的内容
    • 可以使用 [String]()
    • let 定义的数组是不可变的
    • var 定义的数组是可变的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
字典
* OC @{key:value}
* Swift [key:value]
* 和数组一样, 如果用let修饰就是不可变字典, 如果用var修饰就是可变字典
*/
// 开发技巧: 在iOS开发中使用频率最高的就是 [String: AnyObject]
let dict1 = ["name": "lnj", "age": 3]
var dict2:[String: AnyObject]
dict2 = ["name": "lnj", "age": 3]
# 获取
// 注意点: 从字典中取出的值都是可选类型的值
// 因为通过一个key取值, 可能取得到, 也可能取不到, 所以返回一个可选类型的值
// 如果!写在[]后面, 代表取出来的值一定有值
// 如果!写在dict后面, 代表dict一定有值
print(dict2["name"]!)
# 更新
dict2["name"] = "zs"
dict2
# 添加
// 也就是说, 如果key存在就是更新, 如果key不存在就是添加
dict2["score"] = 99
dict2
# 遍历
for key in dict2.keys
{
print(dict2[key])
}
// 会将字典中的key赋值给()中的第一个变量k
// 会将字典中的value赋值给()中的第二个变量v
// 注意: k/v随便写
// ()括起来的东西我们称之为元祖, 元祖是Swift中新增的一种数据类型
for (x, o) in dict2
{
print(x)
print(o)
}
# 合并
var dict3:[String: AnyObject] = ["name": "lnj", "age": 3]
var dict4:[String: AnyObject] = ["score": 99]
// 字典不能像数组一样通过+=来合并
//dict3 += dict4
for (k, v) in dict3
{
// 要在声明时统一字典的格式,不然报错
// 因为如果不加[String: AnyObject],dict3默认是此类型,
// dict4默认是[String: Int],不能把AnyObject给Int赋值
dict4[k] = v
}

字符串

在 Swift 中绝大多数的情况下,推荐使用 String 类型

使用 String 的原因

  • String 是一个结构体,性能更高
    • String 目前具有了绝大多数 NSString 的功能
    • String 支持直接遍历
  • NSString 是一个 OC 对象,性能略差
  • Swift 提供了 StringNSString 之间的无缝转换

遍历字符串

1
2
3
4
5
let str = "我要飞的更High"
for s in str.characters {
print(s)
}

字符串拼接

1
2
3
4
5
6
7
8
let str1 = "zhangsan"
let str2 = "lisi"
let i = 10
// 注意: Swift的print函数不支持%i/%@等占位符
// Swift中拼接内容使用 \(需要拼接的数据)
print(str1 + str2)
print("\(str1) \(str2) \(i)")
  • 阶段性小结
    • 直接在 "" 中使用 \(变量名) 的方式可以快速拼接字符串
    • 小伙伴们再也不要考虑 stringWithFormat

格式化字符串

1
2
3
4
5
6
7
for _ in 0...10 {
let str = String(format: "zhangsan - %04d", arguments: [arc4random_uniform(100)])
print(str)
}
// 15-09-07
let res2 = String(format: "%02d-%02d-%02d", arguments: [15, 9, 7])
  • 阶段性小结
    • 在实际开发中,如果需要指定字符串格式,可以使用 String(format:...) 的方式
    • 注意:后面的参数需要放在一个数组中

String & Range 的结合

以下是超级费劲的代码

1
2
3
4
5
let str: String = "xiaomage"
// 没写完整,很麻烦
var subStr = str.substringWithRange(Range<String.Index>(start: str.startIndex, end: str.endIndex))
print(subStr)

建议写法

1
2
3
4
5
6
7
8
9
10
// 建议: 在Swift开发中如果要截取字符串,
// 或者以前在OC中使用的方法在Swift字符串中没有,
// 建议先将Swift字符串转换为OC字符串之后再使用
let str1: NSString = "xiaomage"
//let str3 = str2 as NSString
//let str3: NSString = str2
print(str1.substringWithRange(NSMakeRange(0, 3)))
let str5:NSString = "user/desktop/abc.txt"
str5.pathExtension

函数

简单演练

1
2
3
func sum(a: Int, b: Int) -> Int {
return a + b
}
  • 阶段性小结
    • 函数定义格式:func 函数名(参数: 参数类型...) -> 返回值 { // 代码实现 }
    • 如果没有返回值, -> 返回值 可以省略
    • -> 是一个很有意思的符号
    • 默认情况下,在调用函数时,第一个参数名是省略的

参数名的特殊处理

强制要求参数名

1
2
3
4
5
// x,y函数调用的时候显示sum1(x: 10, y: 10)
// a,b函数内部使用
func sum1(x a: Int, y b: Int) -> Int {
return a + b
}

省略参数名

1
2
3
4
5
// 不加_调用函数sum1(10, b: 10)
// 加了后调用sum(10,10)
func sum2(a: Int, _ b: Int) -> Int {
return a + b
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#没有返回值没有参数的
func say() -> Void
{}
say()
#如果没有返回值还可以简写
// Void == ()
func say2() -> ()
{}
# 如果没有返回值, 那么返回值类型可以省略
func say3()
{}
# 有返回值没有参数的
// 注意点: 返回值类型必须和返回的值保持一致
func getMax() ->Int
{}
# 没有返回值有参数的
// 从Swift2.0开始, 会自动将第二个参数开始的参数名称作为方法的标签
func sum(a: Int, b: Int)
{
print("sum = \(a + b)")
}
sum(10, b: 20)
# 有返回值有参数的
func sum5(a: Int, b: Int) -> Int
{
return a + b
}
# 已经弃用,代表a/b既是内部参数也是外部参数
func sum(#a: Int, #b: Int)
{
print("sum = \(a + b)")
}
# 默认值
// 如果指定了默认值, 那么调用的时候可以忽略, 如果忽略会使用默认值
// 在其它语言中其实早就有默认值的概念了, 在其它语言中的默认值只能写在形参列表的最后面
// 但是Swift中的默认值可以写在任意地方
func sum3(a: Int, b: Int = 110)
{
print("sum = \(a + b)")
}

闭包

闭包和OC中的Block差不多

1.都是用来保存一段代码, 在适当的时候执行

2.一般用于做一些耗时操作

3.传递值

1
2
3
4
5
6
7
8
类型的格式: (形参列表)->返回值类型
in的作用就是用于分隔需要执行的代码
值的格式:
{
(形参列表)->返回值类型
in
需要执行的代码
}

闭包和Block的区别:

  • Block类似于匿名函数
  • 闭包就是用来定义函数的, Swift中函数其实就是闭包
1
2
3
// 注意: 以下写法是错误的, 代表闭包的返回值是可选的
/* var finished: ()->()? */
正解: var finished: (()->())?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 简写:
func loadData(finished: ()->())
{
// 1.执行耗时操作
print("耗时操作")
// 2.回调通知调用者执行完毕
finished()
}
/*
简写:
1.如果闭包没有参数, 并且没有返回值, 那么in和in之前的代码可以省略
2.如故偶闭包是形参列表的最后一个参数, 那么可以将闭包写到圆括号后面
3.如果形参列表只有一个参数, 并且这个参数是闭包, 那么圆括号可以省略
*/
loadData {
print("收到执行完毕的通知")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
loadData {
# 如果在闭包中使用到了外界对象, 必须加上self
// self.view.backgroundColor = UIColor.redColor()
weakSelf!.view.backgroundColor = UIColor.redColor()
}
loadData { [weak self] () -> () in
print("收到执行完毕的通知")
self!.view.backgroundColor = UIColor.redColor()
}
// Swift中推荐能不写self就不写self,
// OC中weak特点: 如果对象被释放之后会自动赋值给nil
// OC中的__unsafe_unretained特点: 如果对象被释放之后不会自动赋值为nil
// unowned 相当于OC中的__unsafe_unretained, 并且特点一致
loadData { [unowned self] () -> () in
print("收到执行完毕的通知")
self.view.backgroundColor = UIColor.redColor()
}
}
func loadData(finished: ()->())
{
// 0.保存闭包
over = finished
// 1.执行耗时操作
print("耗时操作")
// 2.回调通知调用者执行完毕
finished()
}
// 相当于OC中的dealloc方法, 对象释放的时候就会调用该方法
// 在这个方法中主要进行一些资源的释放操作
deinit{
}

懒加载

  • 格式:
1
lazy var 变量: 类型 = { 创建变量代码 }()
  • 懒加载的写法本质上是定义并执行一个闭包
1
2
3
4
5
6
7
// 含义: 当dataList被使用到时, 就会执行等号后面的闭包
// 所以等号后面的闭包的()是必须写的, 如果不写就会报错
// 注意点: 如果写懒加载, 那么修饰符必须用var
lazy var dataList:[String] = {
print("我被加载了")
return ["lnj", "lmj", "zs"]
}()
1
2
3
4
5
6
lazy var satatuses: [String] = self.loadStatus()
func loadStatus() -> [String]
{
print("我被加载了")
return ["lnj", "lmj", "zs"]
}

属性

1
2
3
Swift中如果文件在同一个命名空间下是不需要导入头文件的
同一个命名空间可以理解为同一个工程
默认情况下, 项目名称就是命名空间的名称,如果想要修改命名空间可以在详见截图

项目名-命名空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var _name: String?
var name: String?{
get{
print("get")
return _name
}
set{
print("set")
// newValue是系统提供给我们的, 只要重写set方法, 就可以通过newValue拿到外界设置的值
_name = newValue
}
}
var name: String?
{
didSet{
print(self)
// 设置完毕之后就会调用
name = "cool"
}
}
// 计算属性, 计算属性不具备存储功能
var age: Int?{
get{
return 100
}
}

getter 和 setter

自定义 Person 类

1
2
3
4
5
class Person: NSObject {
var name: String?
var age: Int?
}

getter & setter

1
2
3
4
5
6
7
8
9
10
var _name: String?
var name: String? {
get {
return _name
}
set {
_name = newValue
}
}
  • Swift 中以上形式的 getter & setter 很少用

didSet

  • 在 OC 中,我们通常希望在给某一个变量赋值之后,去做一些额外的操作
  • 最经典的应用就是在自定义 Cell 的时候,通过模型的设置方法完成 Cell 的填充
1
2
3
4
5
6
var length: Int? {
didSet {
timeStr = String(format: "%02d:%02d:%02d", arguments: [length! / 3600, (length! % 3600) / 60, length! % 60])
}
}
var timeStr: String?

计算型属性

1
2
3
4
5
var title: String {
get {
return "Mr " + (name ?? "")
}
}
  • 只实现 getter 方法的属性被称为计算型属性,等同于 OC 中的 ReadOnly 属性
  • 计算型属性本身不占用内存空间
  • 不可以给计算型属性设置数值
  • 计算型属性可以使用以下代码简写
1
2
3
var title: String {
return "Mr " + (name ?? "")
}

构造函数

注意点: 如果没有在构造方法中对属性进行初始化,
那么对象类型可以写?,基本数据类型最好直接赋值一个初始值

如果在构造方法中给属性赋了初始值, 那么就可以不用写?了,因为只要创建对象就一定会调用构造
方法, 只要调用构造方法那么属性就一定有值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 在Swift中, 如果重写父类的方法, 需要加上override修饰
override init() {
name = "lnj"
age = 3
}
// 自定义构造方法
// 只要自定义了构造方法, 那么系统自带的默认构造方法就会失效
init(name: String, age: Int)
{
// 注意: Swift语法规定, 一定要初始化完当前类之后才能初始化父类
self.name = name
self.age = age
// 注意: 系统悄悄的帮我们调用了一次super.init()
# 因为默认的init没了,所以创建对象要用默认的init(),要override init
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 传入字典构造对象
init(dict: [NSObject: AnyObject]) {
name = dict["name"] as? String
age = dict["age"] as? Int
}
init(dict:[String: AnyObject])
{
// 注意: 在构造方法中使用KVC必须先调用super.init()之后才能使用
// 只有调用了super.init系统才会为属性分配存储空间
super.init()
// 特点: 字典的key必须和模型的属性一一对应
setValuesForKeysWithDictionary(dict)
}
// 利用KVC赋值时, 只要找不到对应的key就会调用这个属性
override func setValue(value: AnyObject?, forUndefinedKey key: String) {
}

析构函数

1
2
3
4
5
// 相当于OC中的dealloc方法, 对象释放的时候就会调用该方法,
// 这个方法中主要进行一些资源的释放操作
deinit {
print("88")
}

重写description

1
2
3
4
5
6
7
8
// 重写打印对象属性
// 如果是计算型的属性, 可以简写
let properties = ["name", "age"]
override var description: String{
// return "name = \(name), age = \(age)"
let dict = dictionaryWithValuesForKeys(properties)
return "\(dict)"
}

1.笨写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class NetworkTools: NSObject {
// USB写法
static var onceToken: dispatch_once_t = 0
static var instance: NetworkTools?
// func 相当于OC中的 -
// class func 相当于OC中的 +
class func shareNetworkTools() -> NetworkTools
{
print("onceToken = \(onceToken)")
dispatch_once(&onceToken) { () -> Void in
print("我被调用了")
instance = NetworkTools()
}
return instance!
}
}

2.标准写法

1
2
3
4
5
6
7
8
9
class NetworkTools: NSObject {
// 这样写也是懒加载的
// 注意: 一定要写let, let本身就是线程安全的, 并且只能赋值一次
static let instance: NetworkTools = NetworkTools()
class func shareNetworkTools() -> NetworkTools
{
return instance
}
}